﻿using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using Windows.Devices.Geolocation;
using Windows.Foundation;

namespace Chenglin.WPMyStep
{
    public class LocationWithTimestamp
    {
        /// <summary>
        /// The time when the location was recorded
        /// </summary>
        [XmlAttribute("t")]
        public DateTime Time;

        /// <summary>
        /// The longitude of the location.
        /// </summary>
        [XmlAttribute("long")]
        public double Longitude;

        /// <summary>
        /// The longitude of the location.
        /// </summary>
        [XmlAttribute("lat")]
        public double Latitude;
    }

    public class LocationTracker
    {
        private readonly object _locker = new object();
        private Geolocator _gpsLocator;
        private PositionStatus _currentStatus;
        private Queue<LocationWithTimestamp> _recentLocations = new Queue<LocationWithTimestamp>();
        private static LocationTracker s_instance = new LocationTracker();

        /// <summary>
        /// The max number of locations to be remembered in the memory.
        /// </summary>
        private const int MaxLocations = 1024;

        /// <summary>
        /// Raised when the location is updated.
        /// </summary>
        public event TypedEventHandler<Geolocator, PositionChangedEventArgs> PositionChanged;

        /// <summary>
        /// Raised when the ability of the Geolocator to provide updated location changes.
        /// </summary>
        public event TypedEventHandler<Geolocator, StatusChangedEventArgs> StatusChanged;

        public static LocationTracker Instance
        {
            get { return s_instance; }
        }

        public PositionStatus Status
        {
            get { return _currentStatus; }
        }

        /// <summary>
        /// constructor
        /// </summary>
        internal LocationTracker()
        { }

        /// <summary>
        /// Get a list of recent locations.
        /// </summary>
        public IList<LocationWithTimestamp> GetRecentLocations()
        {
            LocationWithTimestamp[] array;
            lock (_locker)
            {
                array = new LocationWithTimestamp[_recentLocations.Count];
                _recentLocations.CopyTo(array, 0);
            }

            // We'll only return 64 locations at a time:
            const int MaxCount = 64;
            if (array.Length <= MaxCount)
            {
                return array;
            }

            List<LocationWithTimestamp> list = new List<LocationWithTimestamp>(MaxCount);
            double currentPos = 0;
            double step = ((double)array.Length) / (double)MaxCount;
            for (int i = 0; i < array.Length; i++)
            {
                if (i == (int)Math.Round(currentPos))
                {
                    list.Add(array[i]);
                }

                currentPos += step;
            }
            return list;
        }

        /// <summary>
        /// Start or restart the gps locator. 
        /// </summary>
        public void Start()
        {
            if (_gpsLocator == null)
            {
                lock (_locker)
                {
                    if (_gpsLocator == null)
                    {
                        ReInitGeolocator();
                    }
                }
            }
        }

        /// <summary>
        /// Stops the gps locator.
        /// </summary>
        public LocationWithTimestamp[] Stop()
        {
            lock (_locker)
            {
                StopCurrentGeolocator();

                var locations = _recentLocations.ToArray();
                _recentLocations.Clear();
                return locations;
            }
        }

        private void StopCurrentGeolocator()
        {
            Geolocator original = _gpsLocator;
            _gpsLocator = null;
            if (original != null)
            {
                original.PositionChanged -= Geolocator_PositionChanged;
                original.StatusChanged -= Geolocator_StatusChanged;
                original = null;
            }
        }

        private void ReInitGeolocator()
        {
            StopCurrentGeolocator();

            Geolocator locator = new Geolocator();
            locator.DesiredAccuracy = PositionAccuracy.High;
            locator.MovementThreshold = ApplicationState.State.MovementThreshold; // The units are meters.
            locator.PositionChanged += Geolocator_PositionChanged;
            locator.StatusChanged += Geolocator_StatusChanged;
            _gpsLocator = locator;
        }

        private void Geolocator_StatusChanged(Geolocator sender, StatusChangedEventArgs args)
        {
            _currentStatus = args.Status;
            var handler = this.StatusChanged;
            if (handler != null)
            {
                handler(sender, args);
            }
        }

        private void Geolocator_PositionChanged(Geolocator sender, PositionChangedEventArgs args)
        {
            Geocoordinate coordinate = args.Position.Coordinate;
            lock (_locker)
            {
                if (_recentLocations.Count >= MaxLocations)
                {
                    // Reduce the number of locations by half
                    var newQueue = new Queue<LocationWithTimestamp>(MaxLocations);
                    int pos = 0;
                    foreach (LocationWithTimestamp loc in _recentLocations)
                    {
                        if ((pos & 0x01) != 1)
                        {
                            newQueue.Enqueue(loc);
                        }
                        pos++;
                    }

                    _recentLocations.Clear();
                    _recentLocations = newQueue;
                }

                _recentLocations.Enqueue(new LocationWithTimestamp { Time = DateTime.Now, Latitude = coordinate.Latitude, Longitude = coordinate.Longitude });
            }

            if (!App.RunningInBackground)
            {
                var handler = this.PositionChanged;
                if (handler != null)
                {
                    handler(sender, args);
                }
            }
        }
    }
}
